#include "visualize.hh"
#include "node.hh"
#include "cppn/cppn.hh"
#include "constants.hh"
#include <algorithm>
#include <iterator>
using namespace std;

namespace brain {

    NetworkWindow::NetworkWindow (int w, int h, Builder* builder)
    : builder(builder), window(nullptr), renderer(nullptr),
      width(w), height(h)     
     {
        if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
            cerr << "SDL_Init Error: " << SDL_GetError() << '\n';
            return;
        }
    }

    NetworkWindow::~NetworkWindow() {

        if (renderer != nullptr) {
            SDL_DestroyRenderer(renderer);
        }

        if (window != nullptr) {
            SDL_DestroyWindow(window);
        }
        SDL_Quit();
    }

    void
    NetworkWindow::show () {
        if (window == nullptr) {
            window = SDL_CreateWindow("CPPN Hypercube", 100, 100, width, height, SDL_WINDOW_SHOWN);
            if (window == nullptr) {
                cerr << "SDL_CreateWindow Error: " << SDL_GetError() << '\n';
                return;
            }
        }

        if (renderer == nullptr) {
            renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
            if (renderer == nullptr) return;
        }
    }

    void
    NetworkWindow::plain () {
        clicked = false;
        render();
    }

    void 
    NetworkWindow::click (int x, int y, bool incoming) {
        this->incoming = incoming;

        for (Node const& node: builder->get_input_nodes()) {

            int nx = width * (node.get_x()+1)/2;
            int ny = height * (node.get_y()+1)/2;

            if (abs(x - nx) < 6 && abs(y - ny) < 6) {
                selected = &node;
                clicked = true;
                render();
                return;
            }
        }

        for (Node const& node: builder->get_output_nodes()) {

            int nx = width * (node.get_x()+1)/2;
            int ny = height * (node.get_y()+1)/2;

            if (abs(x - nx) < 6 && abs(y - ny) < 6) {
                selected = &node;
                clicked = true;
                render();
                return;
            }
        }

        for (Node const& node: builder->get_hidden_nodes()) {

            int nx = width * (node.get_x()+1)/2;
            int ny = height * (node.get_y()+1)/2;

            if (abs(x - nx) < 6 && abs(y - ny) < 6) {
                selected = &node;
                clicked = true;
                render();
                return;
            }
        }

        plain();
        render();
        cout << "No node was selected at " << x << ", " << y << endl;
    }

    #include <cassert>

    void
    NetworkWindow::render() {
        if (window == nullptr || renderer == nullptr) show();

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        if (clicked) {
            cppn::CPPN* cppn = builder->get_cppn();
            for (int w = 0; w < width; ++w) {
                    for (int h = 0; h < height; ++h) {
                    const float target_x = 2*w / float(width) - 1;
                    const float target_y = 2*h / float(height) - 1;

                    cppn->reset();
                    float x = selected->get_x();
                    float y = selected->get_y();
                    if (incoming) {
                        cppn->set_input(x, y, target_x, target_y);
                    } else {
                        cppn->set_input(target_x, target_y, x, y);
                    }
                    const float weight = cppn->get_weight();
                    const int value = 100 * abs(weight);
                    const bool leo = cppn->get_leo();

                    if (weight < 0 && leo) {
                        SDL_SetRenderDrawColor(renderer, value*0.6, 0, value, 255);
                    } else if (weight > 0 && leo) {
                        SDL_SetRenderDrawColor(renderer, value*0.6, value, 0, 255);                    
                    } else {
                        SDL_SetRenderDrawColor (renderer, value, 0, 0, 255);                    
                    }

                    SDL_RenderDrawPoint (renderer, w, h);        
                }
            }
        }

        if (clicked) {
            set<Connection> connections;
           
            copy_if(builder->get_connections().begin(), builder->get_connections().end(),
                inserter(connections, connections.begin()), 
                [&] (Connection const& c) { return incoming ? c.get_src() == *selected
                    : c.get_dst() == *selected; });

            cout << "Drawing " << connections.size() << " lines." << endl;
            for (Connection const& c: connections) {
                
                Node src = incoming ? c.get_src() : c.get_dst();
                Node dst = incoming ? c.get_dst() : c.get_src();

                int sx = width * (src.get_x()+1)/2;
                int sy = height * (src.get_y()+1)/2;
                int dx = width * (dst.get_x()+1)/2;
                int dy = height * (dst.get_y()+1)/2;
                float weight = c.get_weight();
                int color = 150 * abs(c.get_weight())/WEIGHT_ABS_RANGE + 100;

                cout << "Weight: " << weight << endl;

                bool out = builder->get_output_nodes().count(incoming ? dst : src) == 1;
                bool in = builder->get_input_nodes().count(incoming ? src : dst) == 1;

                if (out && in) { SDL_SetRenderDrawColor(renderer, color, 20, color, 255); }
                else if (out) { SDL_SetRenderDrawColor(renderer, 20, 20, color, 255); }
                else if (in) { SDL_SetRenderDrawColor(renderer, color, 20, 20, 255); }
                else { SDL_SetRenderDrawColor(renderer, 20, color, 20, 255); }

                SDL_RenderDrawLine(renderer, sx, sy, dx, dy);
            }
        }


        for (Node const& node: builder->get_hidden_nodes()) {
            SDL_SetRenderDrawColor (renderer, 50, 200, 50, 255);

            int x = width * (node.get_x()+1)/2;
            int y = height * (node.get_y()+1)/2;

            SDL_RenderDrawPoint (renderer, x, y);
        }
        for (Node const& node: builder->get_input_nodes()) {
            SDL_SetRenderDrawColor (renderer, 240, 50, 50, 255);

            int x = width * (node.get_x()+1)/2;
            int y = height * (node.get_y()+1)/2;

            SDL_RenderDrawPoint (renderer, x, y);
        }
        for (Node const& node: builder->get_output_nodes()) {
            SDL_SetRenderDrawColor (renderer, 50, 50, 240, 255);

            int x = width * (node.get_x()+1)/2;
            int y = height * (node.get_y()+1)/2;

            SDL_RenderDrawPoint (renderer, x, y);
        }

        SDL_RenderPresent(renderer);
    }

    void
    NetworkWindow::wait(long ms) {
        SDL_Delay(ms);
    }

}














